
#include <d3dx9.h>
#include "Diagram.h"
#include "Preview.h"

//Global objects
HWND                 hwnd2D;
HWND                 hwnd3D;
LPDIRECT3D9          pD3D         = 0;
LPDIRECT3DDEVICE9    pD3DDevice   = 0;
LPDIRECT3DSWAPCHAIN9 pSwapChain3D = 0; 
LPDIRECT3DSWAPCHAIN9 pSwapChain2D = 0; 

CDiagram* diagram = 0;
CPreview* preview = 0;

//Function defines
bool initD3D();
void mainLoop();
void cleanUp();
LRESULT CALLBACK WindowProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);

//Create two windows (under same device) one for 2D and the other for 3D
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
	WNDCLASSEX winClass; 
    
	winClass.lpszClassName = L"Circuit";
	winClass.cbSize        = sizeof(WNDCLASSEX);
	winClass.style         = CS_HREDRAW | CS_VREDRAW;
	winClass.lpfnWndProc   = WindowProc;
	winClass.hInstance     = hInstance;
	winClass.hIcon	       = LoadIcon(hInstance, L"Data/Circuit.ico");
    winClass.hIconSm	   = 0;//LoadIcon(hInstance, (LPCTSTR)IDI_DIRECTX_ICON);
	winClass.hCursor       = LoadCursor(NULL, IDC_ARROW);
	winClass.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH);
	winClass.lpszMenuName  = NULL;
	winClass.cbClsExtra    = 0;
	winClass.cbWndExtra    = 0;

	if(!RegisterClassEx(&winClass))
		return E_FAIL;

	//Create 3D window
	hwnd3D = CreateWindowEx(NULL, L"Circuit", L"Circuit Preview", WS_OVERLAPPEDWINDOW|WS_VISIBLE,
					        0, 0, 800, 600, NULL, NULL, hInstance, NULL);

	if(hwnd3D == NULL)
		return E_FAIL;

    ShowWindow(hwnd3D, nCmdShow);
    UpdateWindow(hwnd3D);

    hwnd2D = CreateWindowEx(NULL, L"Circuit", L"Circuit Diagram", WS_OVERLAPPEDWINDOW|WS_VISIBLE,
					        224, 168, 800, 600, NULL, NULL, hInstance, NULL );

	if(hwnd2D == NULL)
		return E_FAIL;

    ShowWindow(hwnd2D, nCmdShow);
    UpdateWindow(hwnd2D);

	//Initialize Direct3D
	initD3D();

	//Initialize window objects
	diagram = new CDiagram();
	diagram->init(hwnd2D, pD3DDevice, pSwapChain2D);
	preview = new CPreview();
	preview->init(hwnd3D, pD3DDevice, pSwapChain3D);

	//Main loop
	mainLoop();

	//Clean up things
	cleanUp();

    UnregisterClass(L"Circuit", winClass.hInstance);

	return 0;
}

void mainLoop()
{
	MSG uMsg;
	memset(&uMsg,0,sizeof(uMsg));

	while(uMsg.message != WM_QUIT)
	{
		if(PeekMessage(&uMsg, NULL, 0, 0, PM_REMOVE))
		{ 
			TranslateMessage(&uMsg);
			DispatchMessage(&uMsg);
		}
        else
        {
			//Update
            diagram->update();
			preview->update();

			//Render
			diagram->render();
			preview->render();
        }
	}
}

bool initD3D()
{
	//Create the main D3D object
    pD3D = Direct3DCreate9(D3D_SDK_VERSION);
    if(pD3D == NULL)
    {
		 return false;
    }

    //Get the current display mode
    D3DDISPLAYMODE d3ddm;
    if(FAILED(pD3D->GetAdapterDisplayMode(D3DADAPTER_DEFAULT, &d3ddm)))
    {
        return false;
    }

    //Create a structure to hold the settings for our device
    D3DPRESENT_PARAMETERS d3dpp; 
    ZeroMemory(&d3dpp, sizeof(d3dpp));

    //Fill the structure. 
    //We want our program to be windowed, and set the back buffer to a format
    //that matches our current display mode
    d3dpp.Windowed = TRUE;
    d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;
    d3dpp.BackBufferFormat = d3ddm.Format;
	d3dpp.EnableAutoDepthStencil = TRUE;
	d3dpp.AutoDepthStencilFormat = D3DFMT_D16;

	//Enable NVIDIA NVPerfHUD
	UINT adapterToUse     = D3DADAPTER_DEFAULT;
	D3DDEVTYPE deviceType = D3DDEVTYPE_HAL;

	int numberOfAdapters = pD3D->GetAdapterCount();
	for(int i = 0; i < numberOfAdapters; i++)        //For each adapater
	{
		//Look for the "NVIDIA NVPerfHUD" adapter
		D3DADAPTER_IDENTIFIER9 identifier;
		pD3D->GetAdapterIdentifier(i, 0, &identifier);
		if(strcmp(identifier.Description, "NVIDIA NVPerfHUD") == 0)
		{
			adapterToUse = i;
			deviceType   = D3DDEVTYPE_REF;
		}

	}

    //Create a Direct3D device.
	if(FAILED(pD3D->CreateDevice(adapterToUse, deviceType, hwnd3D, 
                                 D3DCREATE_HARDWARE_VERTEXPROCESSING, &d3dpp, &pD3DDevice)))
    {
		//Try software if hardware fails
		if(FAILED(pD3D->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, hwnd3D,
			                         D3DCREATE_SOFTWARE_VERTEXPROCESSING, &d3dpp, &pD3DDevice)))
		{
			return false;
		}
    }

	//Get the first swap chain and create the second
	pD3DDevice->GetSwapChain(0, &pSwapChain3D);
	pD3DDevice->CreateAdditionalSwapChain(&d3dpp, &pSwapChain2D);

	//Setup render states
	pD3DDevice->SetRenderState(D3DRS_ZENABLE, TRUE);
    pD3DDevice->SetRenderState(D3DRS_LIGHTING, FALSE);
    pD3DDevice->SetRenderState(D3DRS_CULLMODE, D3DCULL_NONE);
	return true;
}

void cleanUp()
{
	if(preview)
	{
		preview->cleanUp();
		delete preview;
		preview = 0;
	}

	if(diagram)
	{
		diagram->cleanUp();
		delete diagram;
		diagram = 0;
	}

	if(pSwapChain3D)
	{
		pSwapChain3D->Release();
		pSwapChain3D = 0;
	}
	if(pSwapChain2D)
	{
		pSwapChain2D->Release();
		pSwapChain2D = 0;
	}
	if(pD3DDevice)
	{
		pD3DDevice->Release();
		pD3DDevice = 0;
	}
	if(pD3D)
	{
		pD3D->Release();
		pD3D = 0;
	}
}

LRESULT CALLBACK WindowProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    switch(msg)
	{
		case WM_CLOSE:
			PostQuitMessage(0);
			break;
		
        case WM_DESTROY:
            PostQuitMessage(0);
			break;

		default:
			return DefWindowProc( hWnd, msg, wParam, lParam );
			break;
	}
	return 0;
}